﻿using Newtonsoft.Json;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Reflection.Metadata;

namespace ForguncyPluginPackageTool
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Begin...");
            Console.WriteLine(string.Join(" ", args));

            var projectPath = args[0];
            var forguncyDesignerPath = args[1];

            var forguncyExePath = Path.Combine(forguncyDesignerPath,"Website","designerBin","Forguncy.exe");

            var fileInfo = FileVersionInfo.GetVersionInfo(forguncyExePath);
            var forguncyVersion = new Version(fileInfo.FileVersion);

            Console.WriteLine("ForguncyVersion: " + forguncyVersion);

            var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
            Directory.CreateDirectory(temp);
            try
            {
                File.Copy(Path.Combine(projectPath, "PluginConfig.json"), Path.Combine(temp, "PluginConfig.json"));
                MakeFileNotReadOnly(Path.Combine(temp, "PluginConfig.json"));

                if (File.Exists(Path.Combine(projectPath, "LICENSE.txt")))
                {
                    File.Copy(Path.Combine(projectPath, "LICENSE.txt"), Path.Combine(temp, "LICENSE.txt"));
                    MakeFileNotReadOnly(Path.Combine(temp, "LICENSE.txt"));
                }

                CopyFolder(Path.Combine(projectPath, "Resources"), Path.Combine(temp, "Resources"));

                ClearFolder(temp, projectPath);

                CopyFolder(Path.Combine(projectPath, "bin"), Path.Combine(temp), new HashSet<string>() {
                    "GrapeCity.Forguncy.Plugin.dll",
                    "Forguncy.Commands.dll",
                    "GrapeCity.Forguncy.ServerApi.dll",
                    "GrapeCity.Forguncy.CellTypes.dll"
                });

                File.WriteAllText(Path.Combine(temp, "SourceCodePath"), projectPath);

                var fileName = Path.GetFileName(Path.GetDirectoryName(projectPath));

                var zipFile = Path.Combine(projectPath, "bin", fileName + ".zip");

                if (File.Exists(zipFile))
                {
                    MakeFileNotReadOnly(zipFile);
                    File.Delete(zipFile);
                }

                ZipFile.CreateFromDirectory(temp, zipFile);

                AutoInstallNewPlugin(Path.Combine(projectPath, "PluginConfig.json"), forguncyVersion, temp);
            }
            finally
            {
                Directory.Delete(temp, true);
            }
        }

        private static void AutoInstallNewPlugin(string configPath, Version forguncyVersion, string tempFolder)
        {
            var pluginItem = CreatePluginItem(configPath);
            var fileName = Path.GetFileName(Path.GetDirectoryName(configPath));
            var baseFileName = fileName;
            MarkPluginRemoved((i) => ShouldRemovePluginFolderWhenReplace(pluginItem, i), forguncyVersion);
            var targetFolder = Path.Combine(GetPluginFolder(forguncyVersion), fileName);
            var index = 1;
            while (Directory.Exists(targetFolder))
            {
                fileName = baseFileName + index.ToString();
                targetFolder = Path.Combine(GetPluginFolder(forguncyVersion), fileName);
                index++;
            }
            CopyFolder(tempFolder, targetFolder);
            ClearFolder(targetFolder, tempFolder);
        }

        public static void MakeFileNotReadOnly(string fileName)
        {
            if (File.Exists(fileName))
            {
                if ((File.GetAttributes(fileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                {
                    File.SetAttributes(fileName, File.GetAttributes(fileName) & ~FileAttributes.ReadOnly);
                }
            }
        }

        public static void CopyFolder(string sourcePath, string destinationPath, HashSet<string> skipfiles = null)
        {
            if (!Directory.Exists(destinationPath))
            {
                Directory.CreateDirectory(destinationPath);
            }

            //Now Create all of the directories
            foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
            {
                Directory.CreateDirectory(dirPath.Replace(sourcePath, destinationPath));
            }

            //Copy all the files & Replaces any files with the same name
            foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
            {
                if (skipfiles != null)
                {
                    if (skipfiles.Contains(Path.GetFileName(newPath)))
                    {
                        continue;
                    }
                }
                if (Path.GetExtension(newPath) == ".pdb" ||
                    Path.GetExtension(newPath) == ".ts" ||
                    Path.GetFileName(newPath).ToLower().EndsWith(".js.map") ||
                    Path.GetExtension(newPath) == ".zip")
                {
                    continue;
                }
                var newFile = newPath.Replace(sourcePath, destinationPath);

                File.Copy(newPath, newFile, true);

                MakeFileNotReadOnly(newFile);
            }
        }

        public static void ClearFolder(string dir, string sourceDir)
        {
            foreach (var file in Directory.GetFiles(dir))
            {
                var filename = Path.GetFileName(file);

                if (!File.Exists(Path.Combine(sourceDir, filename)))
                {
                    File.Delete(file);
                }
            }

            foreach (var directoriy in Directory.GetDirectories(dir))
            {
                var directoriyName = Path.GetFileName(directoriy);

                var directoriyPath = Path.Combine(sourceDir, directoriyName);

                if (!Directory.Exists(directoriyPath))
                {
                    Directory.Delete(directoriy, true);
                }
                else
                {
                    ClearFolder(directoriy, directoriyPath);
                }
            }
        }

        private static bool ShouldRemovePluginFolderWhenReplace(PluginItem newItem, PluginItem deletedItem)
        {
            if (newItem.guid != deletedItem.guid)
            {
                return false;
            }

            if (new Version(deletedItem.version) >= new Version(newItem.version))
            //如果新插件版本低，则必需卸载老插件，不然重新加载时还会加载老插件
            {
                return true;
            }

            if (PluginItem.IsSameDependenceVersion(newItem, deletedItem))
            //依赖版本相同，旧插件没有出现可能，因此卸载
            {
                return true;
            }
            else
            //老插件在其他版本desinger中还可能出现，因此保留
            {
                return false;
            }
        }

        private static void MarkPluginRemoved(Predicate<PluginItem> needRemoveFunc, Version version)
        {
            var configs = Directory.GetFiles(GetPluginFolder(version), "PluginConfig.json", SearchOption.AllDirectories);
            foreach (var config in configs)
            {
                var pluginItem = CreatePluginItem(config);

                if (needRemoveFunc(pluginItem))
                {
                    if (!File.Exists(GetDeletedMarkerFileName(config)))
                    {
                        File.Create(GetDeletedMarkerFileName(config));
                    }
                }
            }
        }
        public static string GetPluginFolder(Version version)
        {
            var folder = Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments);

            var pluginFolderName = string.Format("ForguncyPlugin{0}", "v" + version.Major.ToString() + (version.Build >= 100 ? ".1" : ""));

            return Path.Combine(folder, pluginFolderName);
        }

        public static PluginItem CreatePluginItem(string config)
        {
            var text = File.ReadAllText(config);
            var item = JsonConvert.DeserializeObject<PluginItem>(text);
            item.configFilePath = config;
            return item;
        }
        public static string GetDeletedMarkerFileName(string config)
        {
            return Path.Combine(Path.GetDirectoryName(config), "Fgc_deleted_mark");
        }
    }
}